package com.skeyedu.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

/**
 * @Description 描述
 * @Author 羽落陌殇
 * @Date 2021/7/21 9:43
 * @Version 1.0.0
 */
public class UploadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理乱码
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        resp.setCharacterEncoding("UTF-8");

        // 判断上传的文件是普通表单还是带文件的表单
        if (!ServletFileUpload.isMultipartContent(req)) {
            return;
        }
        // 创建文件上传的保存路径, 建议创建在WEB-INF目录下, 安全, 用户无法直接访问上传的文件
        String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
        File uploadFile = new File(uploadPath);
        if (!uploadFile.exists()) {
            uploadFile.mkdir(); // 如果目录不存在, 则创建一个目录
        }

        // 缓存, 临时文件
        // 临时路径, 假如文件大小超过了我们的预期, 我们就把它放到一个临时文件夹中,过几天自动删除，或者提醒用户转换为永久文件
        String uploadTempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
        File uploadTempFile = new File(uploadTempPath);
        if (!uploadTempFile.exists()) {
            uploadTempFile.mkdir(); // 如果目录不存在, 则创建一个目录
        }

        // 处理上传的文件, 一般都是通过输入流来处理, 我们可以使用request.getInputStream(), 原生态的文件上传流的获取文件, 十分麻烦
        // 建议使用 Apache的文件上传组件来实现, commons-fileupload,  它需要依赖于commons-io

        /**
         * ServletFileUpload, 负责处理上传的文件数据, 并将表单中的每个输入项都封装成一个 FileItem 对象
         * 在使用ServletFileUpload 解析请求的时候，需要 DiskFileItemFactory 对象
         * 所以, 我们需要在进行解析工作前构造好 DiskFileItemFactory 对象,
         * 通过 ServletFileUpload 对象的构造方法或者 setFileItemFactory() 设置 ServletFileUpload 的 fileItemFactory 属性。
         */

        // 1、创建DiskFileItemFactory对象, 处理文件上传路径或者大小限制的
        DiskFileItemFactory factory = getDiskFileItemFactory(uploadTempFile);
        // 2、获取ServletFileUpload
        ServletFileUpload upload = getServletFileUpload(factory);
        // 3、处理上传的文件
        String msg = uploadParseRequest(upload, uploadPath, req);

        // 转发信息
        req.setAttribute("msg", msg);
        req.getRequestDispatcher("info.jsp").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    public static DiskFileItemFactory getDiskFileItemFactory (File file) {
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 通过这个工厂设置一个缓冲区, 当上传的文件大于这个缓冲区的时候, 将它放到临时文件夹中
        factory.setSizeThreshold(1024*1024); // 缓冲区大小设置为 1M
        factory.setRepository(file); // 临时目录, 需要的是File
        return factory;
    }

    public static ServletFileUpload getServletFileUpload (DiskFileItemFactory factory) {
        // 2、获取 ServletFileUpload
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 监听文件上传的进度
        upload.setProgressListener(new ProgressListener() {
            /**
             * @param pBytesRead 已读取到的文件大小
             * @param pContentLength 文件大小
             * @param pItems 文件
             */
            @Override
            public void update(long pBytesRead, long pContentLength, int pItems) {
                System.out.println("总大小: " + pContentLength + " 已上传: " + pBytesRead);
            }
        });
        // 处理乱码问题
        upload.setHeaderEncoding("UTF-8");
        // 设置单个文件的最大值 5M
        upload.setFileSizeMax(1024*1024*5);
        // 设置总共能上传的文件的大小 20M
        upload.setSizeMax(1024*1024*20);

        return upload;
    }

    public static String uploadParseRequest(ServletFileUpload upload, String uploadPath, HttpServletRequest request) {
        // 3、处理上传文件, 解析前端的请求, 封装为FileItem对象
        String msg = "";
        try {
            List<FileItem> fileItems = upload.parseRequest(request);
            for (FileItem fileItem : fileItems) {
                // 判断上传的文件是普通表单还是带文件的表单
                if (fileItem.isFormField()) { // 普通表单
                    String name = fileItem.getFieldName();
                    String value = fileItem.getString("UTF-8"); // 处理乱码
                    System.out.println(name + ":" + value);
                } else { // 判断是上传的文件
                    // 拿到文件的路径
                    String uploadFileName = fileItem.getName();
                    System.out.println("上传的文件路径: " + uploadFileName);

                    // 文件名可能存在不合法的情况
                    if (uploadFileName.trim().equals("") || uploadFileName == null) {
                        continue;
                    }

                    // 获得上传的文件名
                    String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
                    // 获得文件的后缀名
                    String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
                    System.out.println("文件信息[文件名: " + fileName + "--- 文件类型" + fileExtName + "]");
                    // UUID 唯一标识的通用码
                    String uuidPath = UUID.randomUUID().toString();
                    // 存到哪？
                    // 文件真实存在的路径
                    String realPath = uploadPath + "\\" + uuidPath;
                    File realPathFile = new File(realPath);
                    if (!realPathFile.exists()) {
                        realPathFile.mkdir(); // 如果目录不存在, 则创建一个目录
                    }


                    // 获取文件上传的输入流
                    InputStream inputStream = fileItem.getInputStream();
                    System.out.println(realPath + "/" + fileName);
                    // 创建一个文件输出流
                    FileOutputStream outputStream = new FileOutputStream(realPath + "\\" + fileName);
                    // 创建缓冲区
                    byte[] buffer = new byte[1024];
                    // 判断文件是否读取完毕
                    int len = 0;
                    // 如果大于 0 说明还有数据
                    while ((len = inputStream.read()) > 0) {
                        outputStream.write(buffer, 0, len);
                    }
                    // 关闭流
                    outputStream.close();
                    inputStream.close();

                    // 消息提示
                    msg = "<h1>文件上传成功!</h1>";
                    fileItem.delete(); // 上传成功, 清除临时文件
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return msg;
    }
}
